﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
using System.Drawing.Imaging;
using System.Drawing.Printing;
using System.IO;
using System.Runtime.InteropServices;

using SsrsRenderStudio.Shared;
using SsrsRenderStudio.Renderer.Support;
using SsrsRenderStudio.KnownTypesAndModels;
using SsrsRenderStudio.Shared.HostServices;
using SsrsRenderStudio.Shared.Windows32API;

namespace SsrsRenderStudio.Renderer.Output.Version2013
{
    public sealed class PrinterOutputSpooler : PrinterOutput
    {
        protected new PrinterOutputParameters Parameters;

        public PrinterOutputSpooler(PrinterOutputParameters parameters) : base(parameters) 
        {
            Parameters = parameters;
        }

        private const float _metricMultiplier = 0.0394f;
        private PageSettings10 _pageSettings = new PageSettings10 { Height = 0f, Width = 0f, MarginLeft = 0.2f, MarginRight = 0.2f, MarginBottom = 0.2f, MarginTop = 0.2f };
        bool _isLandscapeOrientation = false;
        bool _isFlipped = false;

        public override void Process()
        {
            _isFlipped = Math.Abs(Parameters.PageSettings.Rotation) == 180 ? true : false;
            _isLandscapeOrientation = (Parameters.PageSettings.Width > Parameters.PageSettings.Height) ? true : false;

            _pageSettings.Height = (float)(Parameters.PageSettings.Height);
            _pageSettings.Width = (float)(Parameters.PageSettings.Width);

            if (Parameters.PageSettings.IsMetric)
            {
                //TODO might have to swap these
                _pageSettings.Height = (float)(Parameters.PageSettings.Height * _metricMultiplier);
                _pageSettings.Width = (float)(Parameters.PageSettings.Width * _metricMultiplier);

                _pageSettings.MarginLeft = (float)(Parameters.PageSettings.MarginLeft * _metricMultiplier);
                _pageSettings.MarginRight = (float)(Parameters.PageSettings.MarginRight * _metricMultiplier);
                _pageSettings.MarginTop = (float)(Parameters.PageSettings.MarginTop * _metricMultiplier);
                _pageSettings.MarginBottom = (float)(Parameters.PageSettings.MarginBottom * _metricMultiplier);
            }

            Logger.StartSection("printer_process");
            print();
            Logger.LogSection("80010",  "printer_process");
        }

        private void print()
        {
            int hardMarginLeft;
            int hardMarginTop;

            GDI.DOCINFO documentInfo = createDocumentInfo();
            DeviceContextCachingInformation deviceContext = createDevice(new DeviceContextCachingParameters { NetworkDestination = Parameters.PrinterInformation.NetworkDestination, IsCachingEnabled = Parameters.PrinterInformation.CacheDeviceContext, CacheTimeToLive = Parameters.PrinterInformation.DeviceContextTTL });

            if (deviceContext.IsValid)
            {
                Logger.StartSection("printer_getdevicecaps");

                //todo: point of failure?
                hardMarginLeft = GDI.GetDeviceCaps(deviceContext.DeviceContextHandle, (int)GDI.DeviceCap.PHYSICALOFFSETX);
                hardMarginTop = GDI.GetDeviceCaps(deviceContext.DeviceContextHandle,(int)GDI.DeviceCap.PHYSICALOFFSETY);
                hardMarginLeft = (int)(hardMarginLeft * 100F / GDI.GetDeviceCaps(deviceContext.DeviceContextHandle, (int)GDI.DeviceCap.LOGPIXELSX));
                hardMarginTop = (int)(hardMarginTop * 100F / GDI.GetDeviceCaps(deviceContext.DeviceContextHandle, (int)GDI.DeviceCap.LOGPIXELSY));

                Logger.LogSection("80011","printer_getdevicecaps");

                Point[] points = new Point[3] { new Point(0, 0), 
                                            new Point(Convert.ToInt16(_pageSettings.Width * 100), 0), 
                                            new Point(0, Convert.ToInt16(_pageSettings.Height * 100)) };

                
                if (startDocument(deviceContext, ref documentInfo))
                {
                    for (int pageIndex = 0; pageIndex < Metafiles.Length; pageIndex++)
                    {       
                        using (var g = Graphics.FromHdcInternal(deviceContext.DeviceContextHandle))
                        {   
                            g.TranslateTransform(-hardMarginLeft, -hardMarginTop);
                            
                            if (startPage(deviceContext))
                            {
                                float xTrans = 0f;
                                float yTrans = 0f;
                                float qRotate = 0f;

                                lock (this)
                                {
                                    if (_isLandscapeOrientation)
                                    {
                                        if (_isFlipped)
                                        {
                                            qRotate = 90f;
                                            yTrans = (float)(_pageSettings.Height * -100);
                                        }
                                        else
                                        {
                                            qRotate = -90f;
                                            xTrans = (float)(_pageSettings.Width * -100);
                                        }
                                    }
                                    else
                                    {
                                        if (_isFlipped)
                                        {
                                            qRotate = 180;
                                            xTrans = (float)(_pageSettings.Width * -100);
                                            yTrans = (float)(_pageSettings.Height * -100);
                                        }
                                    }

                                    g.RotateTransform(qRotate);

                                    // move origin
                                    g.TranslateTransform(xTrans, yTrans);
                                    MetafilePainterInstance painter = new MetafilePainterInstance();
                                    painter.PaintImage(g, Metafiles[pageIndex], points);

                                    g.Flush();
                                }

                                endPage(deviceContext);
                            }
                        }
                    }

                    endDocument(deviceContext);
                }

                destroyDevice(deviceContext);
            }
        }

        #region GDI Wrappers

        private GDI.DOCINFO createDocumentInfo(string documentName = null)
        {
            if (documentName == null)
                documentName = Properties.Resources.PRINTER_OUTPUT_DOCUMENT_NAME_DEFAULT;

            GDI.DOCINFO documentInfo;
            documentInfo = new GDI.DOCINFO();
            documentInfo.fwType = 0;
            documentInfo.lpszDatatype = null;
            documentInfo.lpszDocName = documentName;
            documentInfo.lpszOutput = null;
            documentInfo.cbSize = Marshal.SizeOf(documentInfo);
            return documentInfo;
        }

        private DeviceContextCachingInformation createDevice(DeviceContextCachingParameters parameters)
        {
            Logger.StartSection("TRACE_PRINTER_DEVICE_CONTEXT_CREATION");
            DeviceContextCachingInformation deviceContextCacheInfo = Parameters.DeviceContextRetrievalFunction(parameters);

            if (!deviceContextCacheInfo.IsValid)
            {
                //todo : need to throw some errros for REX on this.
                Logger.Log("10004",  Parameters.PrinterInformation.NetworkDestination , Marshal.GetLastWin32Error().ToString() );
            }

            Logger.LogSection("80012", "TRACE_PRINTER_DEVICE_CONTEXT_CREATION");
            return deviceContextCacheInfo;
        }

        private void destroyDevice(DeviceContextCachingInformation deviceContext)
        {
            if (deviceContext.IsValid && !deviceContext.WasCached)
            {
                Logger.StartSection("printer_deletedc");
                GDI.DeleteDC(deviceContext.DeviceContextHandle);
                Logger.LogSection("80013", "printer_deletedc");
            }
        }

        private bool startDocument(DeviceContextCachingInformation deviceContext, ref GDI.DOCINFO documentInfo)
        {
            Logger.StartSection("printer_startdoc");
            int startdoc = GDI.StartDoc(deviceContext.DeviceContextHandle, ref documentInfo);
            Logger.LogSection("80014", "printer_startdoc");

            if (startdoc <= 0)
                Logger.Log("10005", Parameters.PrinterInformation.NetworkDestination );

            return startdoc > 0;
        }

        private bool endDocument(DeviceContextCachingInformation deviceContext)
        {
            Logger.StartSection("printer_enddoc");
            int enddoc = GDI.EndDoc(deviceContext.DeviceContextHandle);
            Logger.LogSection("80015","printer_enddoc");

            if (enddoc <= 0)
                Logger.Log("10006", Parameters.PrinterInformation.NetworkDestination);

            return enddoc > 0;
        }

        private bool startPage(DeviceContextCachingInformation deviceContext)
        {
            int startpage = GDI.StartPage(deviceContext.DeviceContextHandle);

            if (startpage <= 0)
                Logger.Log("10007", Parameters.PrinterInformation.NetworkDestination);

            return startpage > 0;
        }

        private bool endPage(DeviceContextCachingInformation deviceContext)
        {
            int endpage = GDI.EndPage(deviceContext.DeviceContextHandle);

            if (endpage <= 0)
                Logger.Log("10008",  Parameters.PrinterInformation.NetworkDestination );

            return endpage > 0;
        }

        #endregion
    }
}
